n/**
 *  Copyright 2013 Nate Dierk
 *
 *  COMP 590 -- Fall 2013
 */

#include <algorithm>

#include "GContext.h"
#include "GBitmap.h"
#include "GColor.h"

class GContext0 : public GContext {
protected:
    GBitmap fBmp;

    void packARGB(GPixel* pixel, const GColor& color){
      // clamp floats between 0 and 1
      float a = std::min(std::max(0.f, color.fA), 1.f);
      float r = std::min(std::max(0.f, color.fR), 1.f);
      float g = std::min(std::max(0.f, color.fG), 1.f);
      float b = std::min(std::max(0.f, color.fB), 1.f);

      // pre-multiply alpha and 255 constant
      uint32_t preA = (uint32_t) (a * GPIXEL_SCALE_FACTOR);
      uint32_t preR = (uint32_t) (a * r * GPIXEL_SCALE_FACTOR);
      uint32_t preG = (uint32_t) (a * g * GPIXEL_SCALE_FACTOR);
      uint32_t preB = (uint32_t) (a * b * GPIXEL_SCALE_FACTOR);

      // apply shift and jam into pixel
      *pixel = GPixel_PackARGB(preA, preR, preG, preB);
    };

public:
    GContext0(const GBitmap& bmp) {
      fBmp = bmp;
    }

    virtual ~GContext0() {}

    /**
     *  Return the information about the context's bitmap.
     */
    void getBitmap(GBitmap* bmp) const {
      *bmp = fBmp;
    }

    /**
     *  Set the entire context's pixels to the specified value.
     */
    void clear(const GColor& color){
      GPixel clearPixel;
      packARGB(&clearPixel, color);

      char* row = (char*) fBmp.fPixels;
      int rowBytes = fBmp.fRowBytes;
      int w = fBmp.fWidth;
      int x, y;

      for (y = 0; y < fBmp.fHeight; ++y){
	for (x = 0; x < w; ++x){
	  ((GPixel*) row)[x] = clearPixel;
	}
	row += rowBytes;
      }
    };

};

class GContext1 : public GContext0 {
public:
  GContext1(const GBitmap& bmp) : GContext0(bmp) { }
  ~GContext1(){  }
};

class GContext2 : public GContext0 {
public:  
  GContext2(const GBitmap& bmp) : GContext0(bmp) {}
  ~GContext2(){
    free(fBmp.fPixels);
  }
};
  
static bool isBmpValid(const GBitmap& bmp){
  return  !(bmp.fHeight   <= 0 || 
	    bmp.fWidth    <= 0 || 
	    bmp.fRowBytes < (bmp.fWidth * sizeof(GPixel)));
}

static bool isMemValid(const GBitmap& bmp){
  return bmp.fPixels != NULL;
}

GContext* GContext::Create(const GBitmap& bmp){
  if (isBmpValid(bmp) && isMemValid(bmp)){
    return new GContext1(bmp);
  } else {
    return NULL;
  }
}

GContext* GContext::Create(int width, int height){
  GBitmap bmp;
  bmp.fWidth = width;
  bmp.fHeight = height;
  bmp.fRowBytes = width * sizeof(GPixel);

  if (isBmpValid(bmp)){
    bmp.fPixels = (GPixel*) malloc(height * bmp.fRowBytes);
    
    if (isMemValid(bmp)){
      const GBitmap& pBmp = bmp;
      return new GContext2(pBmp);
    }
  } else {
    return NULL;
  }
}
